import 'dart:math';

import 'package:flutter/material.dart';

class LinearProgressBar extends StatelessWidget {
  const LinearProgressBar({
    required this.thickness,
    required this.progress,
    required this.backgroundColor,
    required this.progressColor,
    required this.roundedCap,
    this.shadow,
    super.key,
  });

  final double thickness;
  final double progress;
  final Color backgroundColor;
  final Color progressColor;
  final bool roundedCap;
  final BoxShadow? shadow;

  @override
  Widget build(BuildContext context) {
    BoxDecoration? decoration;

    if (shadow != null) {
      BorderRadiusGeometry? borderRadius;

      if (roundedCap) {
        borderRadius = BorderRadius.circular(thickness / 2);
      }

      decoration = BoxDecoration(
        borderRadius: borderRadius,
        boxShadow: [
          shadow!,
        ],
      );
    }

    return Container(
      height: thickness,
      width: double.infinity,
      decoration: decoration,
      child: CustomPaint(
        painter: _LinearProgressBarPainter(
          thickness: thickness,
          progress: progress,
          backgroundColor: backgroundColor,
          roundedCap: roundedCap,
          progressColor: progressColor,
        ),
      ),
    );
  }
}

class _LinearProgressBarPainter extends CustomPainter {
  _LinearProgressBarPainter({
    required this.thickness,
    required this.progress,
    required this.backgroundColor,
    required this.progressColor,
    required this.roundedCap,
  });

  final double thickness;
  final double progress;
  final Color backgroundColor;
  final Color progressColor;
  final bool roundedCap;

  @override
  void paint(Canvas canvas, Size size) {
    final backgroundRoundedPaint = Paint()
      ..color = backgroundColor
      ..strokeWidth = thickness
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final backgroundButtPaint = Paint()
      ..color = backgroundColor
      ..strokeWidth = thickness
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.butt;

    final customCapPaint = Paint()
      ..color = progressColor
      ..style = PaintingStyle.fill;

    final progressRoundPaint = Paint()
      ..color = progressColor
      ..strokeWidth = thickness
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final progressButtPaint = Paint()
      ..color = progressColor
      ..strokeWidth = thickness
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.butt;

    if (roundedCap) {
      canvas.drawLine(
        Offset(thickness / 2, size.height / 2),
        Offset(size.width - thickness / 2, size.height / 2),
        backgroundRoundedPaint,
      );
    } else {
      canvas.drawLine(
        Offset(0, size.height / 2),
        Offset(size.width, size.height / 2),
        backgroundButtPaint,
      );
    }

    if (progress == 0) {
      return;
    }

    if (roundedCap) {
      final capsProgress = thickness / size.width;

      if (progress <= capsProgress) {
        final h = progress * size.width / 2;
        final beta = acos((thickness - h * 2) / thickness);
        canvas.drawArc(
          Rect.fromCircle(
            center: Offset(thickness / 2, size.height / 2),
            radius: thickness / 2,
          ),
          pi - beta,
          2 * beta,
          false,
          customCapPaint,
        );
        canvas.drawArc(
          Rect.fromCircle(
            center: Offset(2 * h - thickness / 2, size.height / 2),
            radius: thickness / 2,
          ),
          -beta,
          2 * beta,
          false,
          customCapPaint,
        );
      } else {
        canvas.drawLine(
          Offset(thickness / 2, size.height / 2),
          Offset(progress * size.width - thickness / 2, size.height / 2),
          progressRoundPaint,
        );
      }
    } else {
      canvas.drawLine(
        Offset(0, size.height / 2),
        Offset(progress * size.width, size.height / 2),
        progressButtPaint,
      );
    }
  }

  @override
  bool shouldRepaint(_LinearProgressBarPainter oldDelegate) {
    return progress != oldDelegate.progress;
  }
}
